Skip to content

Prepare hosted app tests for optional sharding#440

Open
morluto wants to merge 6 commits into
repoprompt:mainfrom
morluto:test-runner-sharding-prep
Open

Prepare hosted app tests for optional sharding#440
morluto wants to merge 6 commits into
repoprompt:mainfrom
morluto:test-runner-sharding-prep

Conversation

@morluto

@morluto morluto commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the runner mechanics and suite metadata needed to canary process-level sharding for hosted app tests. Default CI behavior remains serial because --workers defaults to 1.

What Changed

  • Added Scripts/Fixtures/ci-test-serial-groups.json for shared-resource serial lanes.
  • Extended Scripts/ci_app_test_runner.py with:
    • --ledger
    • --serial-group-policy
    • --workers
    • --print-suite-plan-json
  • Aggregates contract-ledger rows into suite-level tags, runtime estimates, and execution tiers.
  • Classifies suites as pinned serial or parallel eligible.
  • Keeps pinned suites out of the worker pool when --workers > 1.
  • Stops in-flight suite subprocesses through the existing process cleanup path after a failure.
  • Adds runner self-tests for policy parsing, suite planning, serial ordering, worker scheduling, fail-fast behavior, and missing config errors.
  • Adds targeted teardown for known shared-state suites.
  • Updates ledger tags for touched suites without changing scenario counts or method IDs.

Review Notes

Suggested review order:

  1. Scripts/ci_app_test_runner.py
  2. Scripts/test_ci_app_test_runner.py
  3. Scripts/Fixtures/ci-test-serial-groups.json
  4. Scripts/Fixtures/test-suite-contract-ledger.tsv
  5. Targeted XCTest teardown changes under Tests/RepoPromptTests

The main behavior to verify is that --workers 1 preserves current serial execution, while --workers > 1 schedules only parallel-eligible suites in the worker pool.

Validation

Passed:

  • make ci-app-test-runner-selftest
  • python3 -m py_compile Scripts/ci_app_test_runner.py Scripts/test_ci_app_test_runner.py
  • git diff --check
  • make dev-test-shard-plan SHARDS=4
  • .agents/skills/rpce-contribution-check/scripts/preflight.sh push
  • .agents/skills/rpce-contribution-check/scripts/preflight.sh pr-ready

Scope

This PR does not enable parallel hosted CI. It only adds the config, runner behavior, metadata, and targeted teardown needed for a later canary.

@morluto morluto changed the title Prepare app test runner for optional sharding Prepare hosted app tests for optional sharding Jul 9, 2026
…g-prep

# Conflicts:
#	Scripts/Fixtures/test-suite-contract-ledger.tsv
#	Scripts/ci_app_test_runner.py
#	Scripts/test_ci_app_test_runner.py
#	Tests/RepoPromptTests/MCP/MCPReadSearchLatencyDiagnosticsGuardTests.swift
#	Tests/RepoPromptTests/WorkspaceContext/WorkspaceFileContextStoreTests.swift
@morluto morluto marked this pull request as ready for review July 9, 2026 07:11

@baron baron left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intent Analysis

Problem: The hosted CI XCTest runner (Scripts/ci_app_test_runner.py) is strictly serial; the repo wants to canary process-level suite sharding later without changing default CI behavior now.

Before → After: Before: suites run one group at a time with no shared-state classification. After: new --ledger, --serial-group-policy, --workers (default 1), and --print-suite-plan-json; ledger rows aggregate into suite tags/tiers/runtime estimates; suites classify as pinned_serial vs parallel_eligible via the new Scripts/Fixtures/ci-test-serial-groups.json; with --workers > 1, eligible groups run in a thread pool with fail-fast cancellation while pinned groups run serially on the coordinator thread; targeted teardown added to five known shared-state test files. Hosted CI behavior is unchanged: the workflow diff contains only actions/cache@v4→v6 — no --workers flag is passed anywhere, so the parallel path is genuinely dormant.

Evidence: Verified from the head commit (c131e74) directly, not the PR description: --workers defaults to 1 (ci_app_test_runner.py:1394–1398); run_all_suites takes the pre-existing serial loop when workers == 1 (:1230–1246); test_workers_one_preserves_current_serial_execution_order pins that; .github/workflows/ci.yml diff is the cache bump only.

Confidence: High on intent and dormancy. Medium on runtime-estimate metadata accuracy (no linked perf evidence; it only affects planning of an opt-in mode).

Red Flags: (1) A confirmed cancellation-attribution defect in the new workers>1 scheduler (below). (2) The "fail-fast behavior" self-test mocks run_suite_buffered entirely, so the novel cancellation interaction is never exercised. (3) An unrelated cache-action bump is bundled in.

Findings

F1 (blocking, Medium): cancelled peer can be recorded as first failure, corrupting the exit code. Confirmed mechanism by code inspection: run_suite_buffered sets cancellation_event on the worker thread when its result is neither passed nor cancelled (:1023–1027); a peer observing the event returns state="cancelled", exit_code=130 (:748–762); the coordinator processes completed futures sorted by group label (:1314) and treats any non-passed result as first_failure (:1320–1328) — missing the state == "cancelled" → continue guard the pinned branch has at :1300. Because pinned groups run inline on the coordinator thread (:1282–1307), the real failure and a cancelled peer can land in one done batch, and a peer whose label sorts first yields exit 130 and misattributed first failure. Impact is bounded: fail-fast still fails the run (no false pass) and the real failure's buffered output still prints (:1319) — this is exit-code/attribution corruption, not broken fail-fast. A first-pass reviewer reported ~50/100 reproduction; I did not independently reproduce that frequency, but the mechanism does not depend on it. The existing self-test (test_ci_app_test_runner.py:969–1019) mocks run_suite_buffered with a fake that never sets the event or returns cancelled, so it cannot catch this. Fix: add the cancelled-skip guard in the futures branch (safe — a cancelled result implies the real failed future already resolved and cannot be .cancel()ed) plus one self-test whose fake returns a cancelled peer alongside a failure.

F2 (non-blocking, Low): suites missing from the ledger classify parallel_eligible outside --strict-ledger (:312–326, :1063–1081). Inert at workers=1; any future canary invocation should pass --strict-ledger.

F3 (informational): pinned groups on the coordinator thread mean effective concurrency is workers + 1, and pinned suites overlap the worker pool — defensible if lane tags fully partition conflicts; worth a comment before the canary.

F4 (informational): the actions/cache@v6 bump is unrelated to sharding prep; fine to keep, but call it out.

Maintainer-guidance check

Cause-fixing: F1's fix addresses the missing cancelled-state handling, not a symptom. One authority: ledger stays the single tag authority; the policy fixture is a separate, strictly validated input. State safety: teardowns are targeted; no user-state surface touched. Cancellation/partial-success: the guidance explicitly requires checking cancellation behavior before declaring a design safe — F1 is that check failing. Observability: exit code and first-failure attribution are the runner's contract; F1 damages them in the new mode. Boundary-appropriate validation: the affected boundary is the workers>1 scheduler, and the shipped self-tests mock past its one novel racy interaction, so the validation claim is materially incomplete.

Verdict

Request changes — one narrow fix; the rest is sound and I'd merge with it addressed. Dormancy rightly lowers severity (no CI or default-path impact, no false-pass risk), but the workers>1 scheduler is not incidental dormant code — it is the deliverable. Merging preparation whose core new mechanic has a confirmed, cheaply fixable defect means the future canary starts on a known bug. The fix is a ~2-line guard plus one deterministic self-test; requiring it in-PR is the lower-total-cost path. F2–F4 are non-blocking.

When workers>1, fail-fast peers return state=cancelled with exit 130.
Those results must not become first_failure ahead of the real failing
suite (labels sort them first). Mirror the pinned-serial cancelled skip
and cover the futures-batch case with a regression test.
@morluto

morluto commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @baron — addressed the REQUEST_CHANGES item on head 7906e23d:

  • F1 (cancelled peer steals first_failure): the workers>1 futures branch now skips state == "cancelled" when choosing first_failure, matching the pinned-serial path. Exit code / first-failure attribution stay with the real failing suite even when a cancelled peer’s label sorts first in the done batch.
  • Regression test: test_parallel_fail_fast_ignores_cancelled_peers_for_first_failure returns a cancelled peer (exit 130) alongside a failed suite (exit 7) and asserts exit code 7.

Sentry: no sentry[bot] threads on this PR.

Validation: full Scripts/test_ci_app_test_runner.py suite green (51 tests) locally. Hosted CI will re-run on 7906e23d.

Deferred (non-blocking, as you noted): F2 strict-ledger for future canaries; F3 concurrency comment; F4 cache bump callout — left as-is.

Ready for re-review when you have bandwidth.

When --workers > 1, enable --strict-ledger so missing suites cannot
default to parallel_eligible. Document workers+1 peak concurrency with
pinned serial groups and clarify flag help text.
@morluto

morluto commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on 0c0762d8a77fed887c63e5ef26890409dd401699 addressing the remaining non-blocking notes (on top of the earlier first_failure fix):

  • F2: --workers > 1 now implies --strict-ledger (with a warning) so missing ledger suites cannot default to parallel_eligible during a parallel canary. Covered by test_workers_greater_than_one_enables_strict_ledger.
  • F3: documented intentional peak concurrency of workers + 1 (pinned serial on the coordinator thread overlapping the worker pool); lane tags must fully partition shared-state conflicts before a canary.
  • F4: confirming the bundled actions/cache@v4 → v6 bump is unrelated hygiene and kept intentionally (no behavior change to sharding).

Full test_ci_app_test_runner suite: 52 tests green locally. Ready for re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants